package doc;


import geom.Rect;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import seg.appx.Fit;

public class Stroke {

	public static final Color LINE_COLOR = Color.BLACK;
	public static final Color POINT_COLOR = Color.GREEN;
	public static final Color CORNER_COLOR = Color.RED;

	public static final int LINE_WIDTH = 1;
	public static final int POINT_WIDTH = 3;
	public static final int CORNER_WIDTH = 3;
	public static final boolean DRAW_BOUND = false;
	public static final boolean DRAW_ELLIPSE_FIT = true;

	private static Stroke instance = null;

	public static Stroke getFromFile(File file) {
		Stroke stroke = new Stroke();
		stroke.readFromFile(file);
		return stroke;

	}

	public static Stroke get() {
		if (instance == null) {
			instance = new Stroke();
		}
		return instance;
	}

	public void readFromFile(File file) {
		// !!!! single stroke file only !!!!
		Sketch sketch = new Sketch();
		sketch.readFromFile(file);
		Stroke stroke = sketch.getStrokes().get(0);
		setId(stroke.getId());
		setInk(stroke.getInk());

		clear();
	}

	private void clear() {
		setCorners(new int[0]);
		setPoints(null);
		clearFits();
//		bound = null;
	}

	private List<SampledPoint> originalInk;

	private SampledPoint[] points;

	private int[] corners;

	private int id;

	private double MIN_GAP = 5;

	private double MAX_GAP = 10;

	public double getMinGap() {
		return MIN_GAP;
	}

	public void setMinGap(double min_gap) {
		MIN_GAP = min_gap;
	}

	public double getMaxGap() {
		return MAX_GAP;
	}

	public void setMaxGap(double max_gap) {
		MAX_GAP = max_gap;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public Stroke() {
		originalInk = new ArrayList<SampledPoint>();
		corners = new int[0];
		fits = new Vector<Fit>();
	}

//	public void setCDType(CDFactory.TYPE type) {
//		cd = CDFactory.getCD(type);
//		cd.setHost(this);
//	}

	public boolean appendPoint(SampledPoint sp) {
		return originalInk.add(sp);
	}

	public List<SampledPoint> getInk() {
		return originalInk;
	}

	public void setInk(List<SampledPoint> ps) {
		originalInk = ps;
	}

	public int[] getCorners() {
		return corners;
	}

	public void setCorners(int[] index) {
		corners = index;
	}

	public int length() {
		return originalInk.size();
	}

	public SampledPoint getPoint(int i) {
		return points[i];
	}

	public SampledPoint[] getPoints() {
		return points;
	}

	public SampledPoint firstPoint() {
		return points[0];
	}

 	public SampledPoint lastPoint() {
 		return points[points.length - 1];
 	}

	public void setPoints(SampledPoint ps[]) {
		points = ps;
	}

	public void draw(Graphics2D g2) {
		draw(g2, LINE_COLOR, LINE_WIDTH);
	}

	public void draw(Graphics2D g2, Color color, int width) {
		if (length() <= 1)
			return;
//		if (bound != null)
//			bound.draw(g2);
		if (DRAW_BOUND) {
			Rect[] rects = calcBounds();
			for (Rect r : rects) {
				r.draw(g2);
			}
		}

		if (DRAW_ELLIPSE_FIT) {
			for (Fit fit : fits) {
//				ef.draw(g2, firstPoint().toPoint(), lastPoint().toPoint(), getPoint(1));
//				ef.print();
//				System.out.println("draw fit of: " + fit.getClass().getSimpleName());
				fit.drawFit(g2);
			}
		}

		g2.setColor(color);
		g2.setStroke(new BasicStroke(width));

		Iterator<SampledPoint> it = getInk().iterator();
		SampledPoint p1 = it.next();
		while (it.hasNext()) {
			SampledPoint p2 = it.next();
			g2.drawLine((int) p1.getX(), (int) p1.getY(),
						(int) p2.getX(), (int) p2.getY());
			p1 = p2;
		}
	}

	public String toSimpleML() {
		StringBuffer buf = new StringBuffer();
		buf.append("Stroke:");
		buf.append(getId());
		buf.append(":");
		for (SampledPoint point : getPoints()) {
			buf.append(point.toSimpleML());
		}
		return buf.toString();
	}

	public static Stroke parseSimpleML(String string) {
		String[] parts = string.split(":");

		if (parts.length != 3)
			return null;

		Stroke stroke = new Stroke();
		stroke.setId(Integer.parseInt(parts[1]));

		String seq = parts[2];
		int state = 0; // 0 : before (
		               // 1 : between ( and )
		int prev = 0;
		for (int i = 0; i < seq.length(); i++) {
			if (seq.charAt(i) == '(' && state == 0) {
				state = 1;
				prev = i;
			} else if (seq.charAt(i) == ')' && state == 1) {
				state = 0;
				stroke.appendPoint(SampledPoint.parseSimpleML((seq.substring(prev, i+1))));
			}
		}

		return stroke;
	}

	public void preprocess() {
		List<SampledPoint> newPs = new ArrayList<SampledPoint>();
		List<SampledPoint> ps = getInk();

		if (ps.size() < 1) {
			return;
		}

		SampledPoint p = ps.remove(0);
		newPs.add(p);
		while (!ps.isEmpty()) {
			SampledPoint pp = ps.get(0);

			double dis = p.toPoint().distance(pp.toPoint());
			if (dis < getMinGap()) {
				ps.remove(0);
			} else if (dis > getMaxGap()) {
				ps.add(0, new SampledPoint(p.toPoint().linearInterpolate(pp.toPoint(), 0.5)));
			} else {
				p = ps.remove(0);
				newPs.add(p);
			}
		}
		newPs.add(p);

		setInk(newPs);
		points = new SampledPoint[newPs.size()];
		newPs.toArray(points);

//		bound = Rect.ofBound(getPoints());
	}

	public int getSize() {
		return getPoints().length;
	}

	public void drawPoints(Graphics2D g2) {
		if (getPoints() == null)
			return;
		for (int i = 0; i < getSize(); i++) {
			SampledPoint p = getPoint(i);
			drawPoint(g2, p.getX(), p.getY(), POINT_WIDTH, POINT_COLOR);
		}
	}

	public Rect[] calcBounds() {
		if (getPoints() == null)
			return new Rect[0];

		Rect[] bounds = new Rect[getCorners().length+1];
		int prev = 0;
		int i = 0;
		for ( ; i < getCorners().length; i++) {
			bounds[i] = Rect.ofBoundBetween(getPoints(), prev, getCorners()[i]);
			prev = getCorners()[i];
		}
		bounds[i] = Rect.ofBoundBetween(getPoints(), prev, getPoints().length);
		return bounds;
	}



	private Vector<Fit> fits;

	public void clearFits() {
		fits.removeAllElements();
	}

	public void appendFit(Fit fit) {
		fits.add(fit);
	}

//	public Fit[] calcFits() {
//		if (getPoints() == null)
//			return new Ellipse[0];
//
//		Fit[] fits = new Fit[getCorners().length+1];
//		int prev = 0;
//		int i = 0;
//		for ( ; i < getCorners().length; i++) {
//			fits[i] = Primitive.fitAlways(getPoints(), prev, getCorners()[i]);
////			elliFits[i].setInk(getPointsBetween(prev, getCorners()[i]));
//			prev = getCorners()[i];
//		}
//		fits[i] = EllipseFit.fit(getPoints(), prev, getPoints().length);
////		elliFits[i] =
//		return fits;
//	}

	public SampledPoint[] getPointsBetween(int s, int e) {
		SampledPoint[] ps = new SampledPoint[e-s];
		for (int i = 0; i < ps.length; i++) {
			ps[i] = getPoint(i+s);
		}
		return ps;
	}

	public void drawCorners(Graphics2D g2) {
		for (int i = 0; i < getCorners().length; i++) {
			SampledPoint p = getPoint(getCorners()[i]);
			fillPoint(g2, p.getX(), p.getY(), CORNER_WIDTH, CORNER_COLOR);
		}
	}

	public void drawPoint(Graphics2D g2, double x, double y, int d, Color c) {
		g2.setColor(c);
		g2.drawOval((int)(x-d), (int)y-d, 2*d, 2*d);
	}

	public void fillPoint(Graphics2D g2, double x, double y, int d, Color c) {
		g2.setColor(c);
		g2.fillOval((int)(x-d), (int)(y-d), 2*d, 2*d);
	}

	public Vector<Fit> getFits() {
		return fits;
	}
}
